//////////////////////////////////////////////
// BufferCast.h
//
//////////////////////////////////////////////

/// Defines / Macros -------------------------

#pragma once

/// Includes ---------------------------------

// nkMemory
#include "Buffer.h"

// Standards
#include <array>
#include <vector>

/// Internals --------------------------------

namespace nkMemory
{
	template <typename T>
	struct BufferCastDataDescriptor final
	{
		T* _data ;
		unsigned long long _size ;
	} ;
}

/// Class ------------------------------------

namespace nkMemory
{
	template <typename T = unsigned char>
	class BufferCast
	{
		public :

			// Constructors
			BufferCast () noexcept = default ;
			
			BufferCast (unsigned long long size) noexcept
			:	_data (size * sizeof(T))
			{
				if constexpr (std::is_class_v<T>)
				{
					T* ptr = (T*)_data.getData() ;

					for (unsigned long long i = 0 ; i < size ; ++i)
						new (&ptr[i]) T () ;
				}
			}

			BufferCast (T* data, unsigned long long size) noexcept
			:	_data (size * sizeof(T))
			{
				if constexpr (std::is_class_v<T>)
				{
					T* ptr = (T*)_data.getData() ;

					for (unsigned long long i = 0 ; i < size ; ++i)
						new (&ptr[i]) T (data[i]) ;
				}
				else
				{
					if (size)
						memcpy(_data.getData(), data, size * sizeof(T)) ;
				}
			}

			BufferCast (const BufferCast& cast) noexcept
			:	_data (cast.getSize() * sizeof(T))
			{
				// Copy data around
				unsigned long long size = cast.getSize() ;
				T* ptr = (T*)_data.getData() ;
				T* castPtr = (T*)cast._data.getData() ;

				if constexpr (std::is_class_v<T>)
				{
					for (unsigned long long i = 0 ; i < size ; ++i)
						new (&ptr[i]) T (castPtr[i]) ;
				}
				else
					memcpy(ptr, castPtr, size * sizeof(T)) ;
			}

			BufferCast (BufferCast&& cast) noexcept
			:	_data (std::move(cast._data))
			{
				// Nothing to do
			}

			template <typename U = T>
			BufferCast (std::enable_if_t<!std::is_class_v<U>, const Buffer&> buffer) noexcept
			:	_data (buffer)
			{
				// Nothing to do
			}

			// Destructor
			~BufferCast ()
			{
				if constexpr (!std::is_trivially_destructible_v<T>)
				{
					T* ptr = (T*)_data.getData() ;
					unsigned long long size = getSize() ;

					for (unsigned long long i = 0 ; i < size ; ++i)
						ptr[i].~T() ;
				}
			}

		public :

			// Getters
			T* getData () const
			{
				return (T*)_data.getData() ;
			}

			unsigned long long getSize () const
			{
				return _data.getSize() / sizeof(T) ;
			}

			unsigned long long getByteSize () const
			{
				return _data.getSize() ;
			}

			bool empty () const
			{
				return _data.empty() ;
			}

			T& front ()
			{
				T* ptr = (T*)_data.getData() ;
				return ptr[0] ;
			}

			const T& front () const
			{
				T* ptr = (T*)_data.getData() ;
				return ptr[0] ;
			}

			T& back ()
			{
				T* ptr = (T*)_data.getData() ;
				return ptr[getSize() - 1] ;
			}

			const T& back () const
			{
				T* ptr = (T*)_data.getData() ;
				return ptr[getSize() - 1] ;
			}

			T* begin ()
			{
				return (T*)_data.getData() ;
			}

			const T* begin () const
			{
				return (T*)_data.getData() ;
			}

			T* end ()
			{
				T* ptr = (T*)_data.getData() ;
				return &ptr[getSize()] ;
			}

			const T* end () const
			{
				T* ptr = (T*)_data.getData() ;
				return &ptr[getSize()] ;
			}

		public :

			// Management
			BufferCast<T>& clear ()
			{
				if constexpr (std::is_class_v<T>)
				{
					T* ptr = (T*)_data.getData() ;
					unsigned long long size = getSize() ;

					for (unsigned long long i = 0 ; i < size ; ++i)
						ptr[i].~T() ;
				}

				_data.clear() ;
				return *this ;
			}

			BufferCast<T>& resize (unsigned long long size)
			{
				// Safety check
				if (!size)
				{
					clear() ;
					return *this ;
				}

				// Copy into new memory spot
				Buffer newData (size * sizeof(T)) ;
				T* newPtr = (T*)newData.getData() ;
				T* oldPtr = (T*)_data.getData() ;

				unsigned long long selfSize = getSize() ;
				unsigned long long range = (size < selfSize) ? size : selfSize ;

				// Check if we have a complex type or not
				if constexpr (std::is_class_v<T>)
				{
					// Check if we can move or not the diff
					if constexpr (std::is_move_assignable_v<T>)
					{
						for (unsigned long long i = 0 ; i < range ; ++i)
							new (&newPtr[i]) T (std::move(oldPtr[i])) ;
					}
					else
					{
						for (unsigned long long i = 0 ; i < range ; ++i)
							new (&newPtr[i]) T (oldPtr[i]) ;
					}

					// Initialize if we created new slots
					for (unsigned long long i = range ; i < size ; ++i)
						new (&newPtr[i]) T () ;

					// Delete if we erased some
					for (unsigned long long i = range ; i < selfSize ; ++i)
						oldPtr[i].~T() ;
				}
				else
					memcpy(newPtr, oldPtr, range * sizeof(T)) ;

				_data = std::move(newData) ;
				return *this ;
			}

			T& append (const T& value)
			{
				unsigned long long size = getSize() ;
				resize(size + 1) ;

				T* ptr = getData() ;
				ptr[size] = value ;

				return ptr[size] ;
			}

			template <typename U = T>
			typename std::enable_if_t<std::is_class_v<U>, T&>
			append (T&& value)
			{
				unsigned long long size = getSize() ;
				resize(size + 1) ;

				T* ptr = getData() ;
				ptr[size] = std::forward<T>(value) ;

				return ptr[size] ;
			}

			BufferCast<T>& erase (unsigned long long index, unsigned long long count = 1ull)
			{
				// Do we clear or trim right side ?
				unsigned long long selfSize = getSize() ;

				if (!count)
					return *this ;
				else if (index == 0 && count == selfSize)
					clear() ;
				else if (index + count >= selfSize)
					resize(index) ;
				else
				{
					// Allocate new memory
					Buffer newData ((selfSize - count) * sizeof(T)) ;
					T* newPtr = (T*)newData.getData() ;
					T* oldPtr = (T*)_data.getData() ;

					// Rewrite our entries left
					if constexpr (std::is_class_v<T>)
					{
						if constexpr (std::is_move_assignable_v<T>)
						{
							for (unsigned long long i = 0 ; i < index ; ++i)
								new (&newPtr[i]) T (std::move(oldPtr[i])) ;

							unsigned long long secondStart = index + count ;

							for (unsigned long long i = 0 ; i < selfSize - secondStart ; ++i)
								new (&newPtr[index + i]) T (std::move(oldPtr[secondStart + i])) ;
						}
						else
						{
							for (unsigned long long i = 0 ; i < index ; ++i)
								new (&newPtr[i]) T (oldPtr[i]) ;

							unsigned long long secondStart = index + count ;

							for (unsigned long long i = 0 ; i < selfSize - secondStart ; ++i)
								new (&newPtr[index + i]) T (oldPtr[secondStart + i]) ;
						}

						// Free what needs to be freed
						for (unsigned long long i = index ; i < index + count ; ++i)
							oldPtr[i].~T() ;
					}
					else
					{
						// Copy left and right sides around erase memory
						memcpy(newPtr, oldPtr, index * sizeof(T)) ;
						memcpy(newPtr + index, oldPtr + index + count, (selfSize - index - count) * sizeof(T)) ;
					}

					// Free and update
					_data = std::move(newData) ;
				}

				return *this ;
			}

			template <typename U = T>
			typename std::enable_if_t<!std::is_class_v<U> || std::is_trivially_destructible_v<U>, Buffer>
			relinquishBufferOwnership ()
			{
				return std::move(_data) ;
			}

			BufferCastDataDescriptor<T> relinquishDataOwnership ()
			{
				BufferCastDataDescriptor<T> result ;
				result._size = getSize() ;
				result._data = (T*)_data.relinquishDataOwnership()._data ;

				return result ;
			}

		public :

			// Operators
			T& operator[] (unsigned long long index)
			{
				return getData()[index] ;
			}

			const T& operator[] (unsigned long long index) const
			{
				return getData()[index] ;
			}
			
			BufferCast<T>& operator= (const BufferCast<T>& other) noexcept
			{
				// Safety for self assignment
				if (this == &other)
					return *this ;

				clear() ;

				// If empty, stop there
				if (other.empty())
					return *this ;

				unsigned long long size = other.getSize() ;
				_data = Buffer(size * sizeof(T)) ;

				if constexpr (std::is_class_v<T>)
				{
					T* ptr = (T*)_data.getData() ;
					T* otherPtr = (T*)other._data.getData() ;

					for (unsigned long long i = 0 ; i < size ; ++i)
						new (&ptr[i]) T (otherPtr[i]) ;
				}
				else
					memcpy(_data.getData(), other._data.getData(), size * sizeof(T)) ;

				return *this ;
			}

			template <typename U = T>
			std::enable_if_t<!std::is_class_v<U> || std::is_move_assignable_v<U>, BufferCast<T>&>
			operator= (BufferCast<T>&& other) noexcept
			{
				// Safety for self assignment
				if (this == &other)
					return *this ;

				// Clear ourselves and copy data over here
				clear() ;

				// Safety check
				if (other.empty())
					return *this ;

				unsigned long long size = other.getSize() ;
				_data = Buffer(size * sizeof(T)) ;

				if constexpr (std::is_class_v<T>)
				{
					T* ptr = (T*)_data.getData() ;
					T* otherPtr = (T*)other._data.getData() ;

					for (unsigned long long i = 0 ; i < size ; ++i)
						new (&ptr[i]) T (std::move(otherPtr[i])) ;
				}
				else
					memcpy(_data.getData(), other._data.getData(), size * sizeof(T)) ;

				// Clear other array
				other.clear() ;

				return *this ;
			}

		public :

			// Templated constructors
			template <std::size_t S>
			BufferCast (const std::array<T, S>& array) noexcept
			:	_data (S * sizeof(T))
			{
				// Copy data around
				if constexpr (std::is_class_v<T>)
				{
					T* ptr = (T*)_data.getData() ;

					for (unsigned long long i = 0 ; i < S ; ++i)
						new (&ptr[i]) T (array[i]) ;
				}
				else
				{
					if (!array.empty())
						memcpy(_data.getData(), array.data(), S * sizeof(T)) ;
				}
			}

			template <typename U, std::size_t S>
			BufferCast (const std::array<U, S>& array) noexcept
			:	_data (S * sizeof(T))
			{
				// Copy around
				T* ptr = (T*)_data.getData() ;

				if constexpr (std::is_class_v<T>)
				{
					for (unsigned long long i = 0 ; i < S ; ++i)
						new (&ptr[i]) T (array[i]) ;
				}
				else
				{
					for (unsigned long long i = 0 ; i < S ; ++i)
						ptr[i] = array[i] ;
				}
			}

			BufferCast (const std::vector<T>& vec) noexcept
			:	_data (vec.size() * sizeof(T))
			{
				// Copy data around
				size_t size = vec.size() ;
				size_t byteSize = size * sizeof(T) ;
				T* ptr = (T*)_data.getData() ;
				const T* vecPtr = vec.data() ;

				if constexpr (std::is_class_v<T>)
				{
					for (size_t i = 0 ; i < size ; ++i)
						new (&ptr[i]) T (vecPtr[i]) ;
				}
				else
					memcpy(ptr, vecPtr, byteSize) ;
			}

			template <typename U>
			BufferCast (const std::vector<U>& vec) noexcept
			:	_data (vec.size() * sizeof(T))
			{
				// Copy data around
				size_t size = vec.size() ;
				T* ptr = (T*)_data.getData() ;

				if constexpr (std::is_class_v<T>)
				{
					for (size_t i = 0 ; i < size ; ++i)
						new (&ptr[i]) T (vec[i]) ;
				}
				else
				{
					for (size_t i = 0 ; i < size ; ++i)
						ptr[i] = vec[i] ;
				}
			}

			BufferCast (std::initializer_list<T> args) noexcept
			:	_data (args.size() * sizeof(T))
			{
				// Copy data around
				size_t size = args.size() ;
				T* ptr = (T*)_data.getData() ;
				const T* in = args.begin() ;

				if constexpr (std::is_class_v<T>)
				{
					for (size_t i = 0 ; i < size ; ++i)
						new (&ptr[i]) T (in[i]) ;
				}
				else
				{
					for (size_t i = 0 ; i < size ; ++i)
						ptr[i] = in[i] ;
				}
			}

			template <typename U>
			BufferCast (std::initializer_list<U> args) noexcept
			:	_data (args.size() * sizeof(T))
			{
				// Copy data around
				size_t size = args.size() ;
				T* ptr = (T*)_data.getData() ;
				const U* in = args.begin() ;

				if constexpr (std::is_class_v<T>)
				{
					for (size_t i = 0 ; i < size ; ++i)
						new (&ptr[i]) T (in[i]) ;
				}
				else
				{
					for (size_t i = 0 ; i < size ; ++i)
						ptr[i] = in[i] ;
				}
			}

		private :

			// Attributes
			// Base buffer
			nkMemory::Buffer _data ;
	} ;
}